package cool.taomu.toolkit.demo.stock

import cool.taomu.toolkit.demo.stock.entity.StockHistory
import java.util.List
import org.slf4j.LoggerFactory

class SignalUtils {
    val static LOG = LoggerFactory.getLogger(SignalUtils)

    /**
     * 判断是否为锤子线（底部反转信号，买入）
     */
    def static boolean isHammer(StockHistory k) {
        var double body = Math.abs(k.getClose() - k.getOpen());
        var double height = k.getHigh() - k.getLow();
        if(height == 0) return false;

        var double upperShadow = k.getHigh() - Math.max(k.getClose(), k.getOpen());
        var double lowerShadow = Math.min(k.getClose(), k.getOpen()) - k.getLow();

        // 实体小、下影线长（>2倍实体）、上影线极短、出现在低位
        return body <= height * 0.3 && lowerShadow >= height * 0.6 && upperShadow <= height * 0.1 &&
            k.getClose() > k.getOpen(); // 阳线更可靠
    }

    /**
     * 判断是否为吊颈线（顶部反转信号，卖出）
     */
    def static boolean isHangingMan(StockHistory k) {
        var double body = Math.abs(k.getClose() - k.getOpen());
        var double height = k.getHigh() - k.getLow();
        if(height == 0) return false;

        var double upperShadow = k.getHigh() - Math.max(k.getClose(), k.getOpen());
        var double lowerShadow = Math.min(k.getClose(), k.getOpen()) - k.getLow();

        return body <= height * 0.3 && lowerShadow >= height * 0.6 && upperShadow <= height * 0.1 &&
            k.getClose() < k.getOpen(); // 阴线确认
    }

    /**
     * EMA5 上穿 EMA10：金叉（买入）
     */
    def static boolean isEMAGoldenCross(StockHistory curr, StockHistory prev) {
        return prev.getEma5() <= prev.getEma10() && curr.getEma5() > curr.getEma10();
    }

    /**
     * EMA5 下穿 EMA10：死叉（卖出）
     */
    def static boolean isEMADeathCross(StockHistory curr, StockHistory prev) {
        return prev.getEma5() >= prev.getEma10() && curr.getEma5() < curr.getEma10();
    }

    /**
     * 判断当前股价是否在M20均线上方
     */
    def static boolean isPriceAboveM20(StockHistory bar) {
        return bar.getClose() > bar.getM20();
    }

    /**
     * 判断当前成交量是否大于成交量均线（默认为20日）
     */
    def static boolean isVolumeAboveMa(List<StockHistory> bars, int index, int maPeriod) {
        var double volumeMa = 0;
        for (var int i = Math.max(0, index - maPeriod + 1); i <= index; i++) {

            volumeMa += bars.get(i).getVolume();
        }
        volumeMa /= maPeriod;

        return bars.get(index).getVolume() > volumeMa;
    }

    /**
     * 判断是否为多头排列：M5 > M10 > M20 > M60
     * 可配置容忍度（避免因小数点差异误判）
     */
    def static boolean isMaBullishArrangement(StockHistory bar, double tolerance) {
        var double m5 = bar.getM5();
        var double m10 = bar.getM10();
        var double m20 = bar.getM20();
        var double m60 = bar.getM60();

        return m5 >= m10 * (1 - tolerance) && // M5 >= M10 (允许微小误差)
        m10 >= m20 * (1 - tolerance) && m20 >= m60 * (1 - tolerance);
    }

    def static void detectSignals(String stockCode, List<StockHistory> dataList, SignalHandler handler) {
        if (dataList === null || dataList.size() < 2) {
            LOG.info("历史数据数量不足不能计算")
            return;
        }
        var double tolerance = 0.001;
        var int volumeMaPeriod = 20;
        LOG.info("开始计算")
        for (var int i = 1; i < dataList.size(); i++) {

            var StockHistory prev = dataList.get(i - 1);
            var StockHistory curr = dataList.get(i);

            if (isPriceAboveM20(curr)) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "20日均线上");
            }

            if (isVolumeAboveMa(dataList, i,volumeMaPeriod)) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "当前成交量大于成交量均线");
            }
            if (isMaBullishArrangement(curr, tolerance)) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "多头排列");
            }

            // --- 1. 锤子线：买入 ---
            if (isHammer(curr)) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "K锤子线");
            }

            // --- 2. 吊颈线：卖出 ---
            if (isHangingMan(curr)) {
                handler.onSignal(stockCode, curr.getDate(), "sell", "K吊颈线");
            }

            // --- 3. EMA5 金叉 EMA10 ---
            if (isEMAGoldenCross(curr, prev)) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "EMA 金叉");
            }

            // --- 4. EMA5 死叉 EMA10 ---
            if (isEMADeathCross(curr, prev)) {
                handler.onSignal(stockCode, curr.getDate(), "sell", "EMA 死叉");
            }

            // 假设prev和curr分别代表前一天和当前天的数据对象
            // prev.getMacdDif() 和 curr.getMacdDif() 分别获取前一天和当前天的DIF值
            // prev.getMacdDea() 和 curr.getMacdDea() 分别获取前一天和当前天的DEA值
            // 计算前一天和当天的MACD柱状图值（DIF-DEA）
            var prevMacdBar = prev.getMacdDif() - prev.getMacdDea();
            var currMacdBar = curr.getMacdDif() - curr.getMacdDea();

            // MACD 金叉/死叉并结合0轴判断
            if (prev.getMacdDif() < prev.getMacdDea() && curr.getMacdDif() > curr.getMacdDea()) {
                // 检查是否穿过0轴
                if (prevMacdBar <= 0 && currMacdBar > 0 && curr.getMacdDif() < 0) {
                    handler.onSignal(stockCode, curr.getDate(), "buy", "MACD_00");
                } else if (prevMacdBar <= 0 && currMacdBar > 0) {
                    handler.onSignal(stockCode, curr.getDate(), "buy", "MACD_0");
                } else {
                    handler.onSignal(stockCode, curr.getDate(), "buy", "MACD");
                }
            }
            if (prev.getMacdDif() > prev.getMacdDea() && curr.getMacdDif() < curr.getMacdDea()) {
                // 检查是否穿过0轴
                if (prevMacdBar >= 0 && currMacdBar < 0 && curr.getMacdDif() > 0) {
                    handler.onSignal(stockCode, curr.getDate(), "sell", "MACD_00");
                } else if (prevMacdBar >= 0 && currMacdBar < 0) {
                    handler.onSignal(stockCode, curr.getDate(), "sell", "MACD_0");
                } else {
                    handler.onSignal(stockCode, curr.getDate(), "sell", "MACD");
                }
            }

            // RSI6 超买超卖
            if (curr.getRsi6() < 30) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "RSI");
            }
            if (curr.getRsi6() > 70) {
                handler.onSignal(stockCode, curr.getDate(), "sell", "RSI");
            }

            // KDJ 金叉/死叉
            if (prev.getK() < prev.getD() && curr.getK() > curr.getD()) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "KDJ");
            }
            if (prev.getK() > prev.getD() && curr.getK() < curr.getD()) {
                handler.onSignal(stockCode, curr.getDate(), "sell", "KDJ");
            }

            // 布林带突破下轨后反弹（买入）
            if (prev.getClose() <= prev.getBollLower() && // 昨日价格低于下轨
            curr.getClose() > curr.getBollLower() && // 今日价格高于下轨
            curr.getClose() > prev.getClose() // 并且价格上涨
            ) {
                handler.onSignal(stockCode, curr.getDate(), "buy", "BOLL");
            }

            // 布林带突破上轨后下跌（卖出）
            if (prev.getClose() >= prev.getBollUpper() && // 昨日价格高于上轨
            curr.getClose() < curr.getBollUpper() && // 今日价格低于上轨
            curr.getClose() < prev.getClose() // 并且价格下跌
            ) {
                handler.onSignal(stockCode, curr.getDate(), "sell", "BOLL");
            }

            // OBV 正背离（价格创新低，但 OBV 没有创新低）=> 买入信号
            if (curr.getClose() < prev.getClose() && // 价格下降
            curr.getObv() > prev.getObv()) { // OBV 上升
                handler.onSignal(stockCode, curr.getDate(), "buy", "OBV");
            }

            // OBV 负背离（价格创新高，但 OBV 没有创新高）=> 卖出信号
            if (curr.getClose() > prev.getClose() && // 价格上涨
            curr.getObv() < prev.getObv()) { // OBV 下降
                handler.onSignal(stockCode, curr.getDate(), "sell", "OBV");
            }
        }
    }
}
